```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Redis实现消息队列方案详解</title>
    <link href="https://cdn.staticfile.org/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
    <link href="https://cdn.staticfile.org/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/mermaid@latest/dist/mermaid.min.js"></script>
    <style>
        body {
            font-family: 'Noto Sans SC', Tahoma, Arial, Roboto, "Droid Sans", "Helvetica Neue", "Droid Sans Fallback", "Heiti SC", "Hiragino Sans GB", Simsun, sans-serif;
            color: #333;
            line-height: 1.6;
        }
        .heading {
            font-family: 'Noto Serif SC', serif;
        }
        .prose {
            max-width: 800px;
        }
        .code-block {
            background-color: #2d2d2d;
            color: #f8f8f2;
            border-radius: 6px;
            padding: 1rem;
            overflow-x: auto;
        }
        .highlight {
            background: linear-gradient(90deg, rgba(56,182,255,0.1) 0%, rgba(56,182,255,0) 100%);
            border-left: 4px solid #38b6ff;
            padding: 1rem;
            border-radius: 0 4px 4px 0;
        }
        .card-hover:hover {
            transform: translateY(-5px);
            box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
        }
    </style>
</head>
<body class="bg-gray-50">
    <!-- Hero Section -->
    <div class="relative bg-gradient-to-r from-blue-600 to-indigo-800 py-20 md:py-32">
        <div class="container mx-auto px-4 md:px-6 text-center">
            <h1 class="heading text-4xl md:text-5xl font-bold text-white mb-4">Redis 消息队列实现方案</h1>
            <p class="text-xl text-blue-100 max-w-2xl mx-auto">探索 Redis 在轻量级消息队列应用中的两种实现模式</p>
            <div class="mt-10 flex justify-center">
                <div class="inline-flex rounded-md shadow">
                    <a href="#publish-subscribe" class="inline-flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-indigo-600 bg-white hover:bg-indigo-50 transition duration-300">
                        发布订阅模式
                    </a>
                </div>
                <div class="ml-3 inline-flex rounded-md shadow">
                    <a href="#producer-consumer" class="inline-flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-white bg-indigo-500 bg-opacity-60 hover:bg-opacity-70 transition duration-300">
                        生产消费模式
                    </a>
                </div>
            </div>
        </div>
        <div class="absolute bottom-0 left-0 right-0 h-16 bg-gradient-to-t from-gray-50 to-transparent"></div>
    </div>

    <!-- Main Content -->
    <div class="container mx-auto px-4 md:px-6 py-12">
        <!-- Introduction -->
        <div class="prose mx-auto mb-16">
            <div class="highlight mb-8">
                <p class="text-lg text-gray-700">如果要实现完整的消息队列能力，比如实现可靠性保证、持久化、广播模式、延时队列等功能，使用 Redis 来实现 MQ 显然是不明智的。但如果不想引入一个重量级的MQ组件，仅仅想借用Redis来实现简单的MQ也是可以的。</p>
            </div>
            
            <h2 class="heading text-3xl font-bold mb-6 text-gray-800 border-b pb-2">Redis 实现 MQ 的两种方案</h2>
            <p class="text-lg text-gray-700">Redis 实现消息队列主要有两种方案：发布订阅模式和生产消费模式。</p>
            
            <div class="my-8">
                <img src="https://cdn.nlark.com/yuque/0/2024/png/21449790/1705975330396-879270da-ca50-4a18-b88f-9f9ebe63efa4.png" alt="Redis MQ 两种模式" class="rounded-lg shadow-lg w-full">
            </div>
        </div>

        <!-- Two Models Section -->
        <div class="prose mx-auto mb-20">
            <h2 class="heading text-3xl font-bold mb-6 text-gray-800 border-b pb-2" id="two-models">两种消息模型</h2>
            <p class="text-lg text-gray-700">一般来说，消息队列有两种场景，一种是发布者订阅者模式，一种是生产者消费者模式。利用redis这两种场景的消息队列都能够实现。</p>
            
            <div class="grid md:grid-cols-2 gap-8 mt-8">
                <div class="bg-white p-6 rounded-xl shadow-md border border-gray-100 card-hover transition duration-300">
                    <div class="flex items-center mb-4">
                        <div class="bg-blue-100 p-3 rounded-full mr-4">
                            <i class="fas fa-bullhorn text-blue-600 text-xl"></i>
                        </div>
                        <h3 class="heading text-xl font-bold text-gray-800">生产者消费者模式</h3>
                    </div>
                    <p class="text-gray-700">生产者生产消息放到队列里，多个消费者同时监听队列，谁先抢到消息谁就会从队列中取走消息；即对于每个消息只能被最多一个消费者拥有。</p>
                </div>
                
                <div class="bg-white p-6 rounded-xl shadow-md border border-gray-100 card-hover transition duration-300">
                    <div class="flex items-center mb-4">
                        <div class="bg-purple-100 p-3 rounded-full mr-4">
                            <i class="fas fa-broadcast-tower text-purple-600 text-xl"></i>
                        </div>
                        <h3 class="heading text-xl font-bold text-gray-800">发布者订阅者模式</h3>
                    </div>
                    <p class="text-gray-700">发布者生产消息放到队列里，多个监听队列的消费者都会收到同一份消息；即正常情况下每个消费者收到的消息应该都是一样的。</p>
                </div>
            </div>
            
            <div class="my-8">
                <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1652597938520-11a1d03b-426e-4ed4-8c62-28aae9ef1cbf.png" alt="Redis 消息队列示意图" class="rounded-lg shadow-lg w-full">
            </div>
        </div>

        <!-- Publish Subscribe Section -->
        <div class="prose mx-auto mb-20">
            <h2 class="heading text-3xl font-bold mb-6 text-gray-800 border-b pb-2" id="publish-subscribe">发布订阅模型</h2>
            <p class="text-lg text-gray-700">Redis的发布/订阅模式允许多个客户端同时订阅某个频道，当有消息发布到该频道时，所有订阅该频道的客户端都会接收到这个消息。这种模式使得Redis能够作为消息队列来传递消息。</p>
            
            <div class="grid md:grid-cols-2 gap-8 my-8">
                <div>
                    <img src="https://cdn.nlark.com/yuque/0/2024/png/21449790/1705544763209-c4e4e4f9-660b-412c-9358-55606b94ecb8.png" alt="发布订阅模式图示" class="rounded-lg shadow-lg w-full">
                </div>
                <div>
                    <img src="https://cdn.nlark.com/yuque/0/2024/png/21449790/1705544637336-09d7867e-300f-4dae-98d6-c509f44398c5.png" alt="发布订阅流程" class="rounded-lg shadow-lg w-full">
                </div>
            </div>
            
            <div class="highlight mb-8">
                <h4 class="font-bold text-lg text-gray-800 mb-3">消息生产和消费流程：</h4>
                <ol class="list-decimal pl-5 space-y-2">
                    <li>定义channel，然后消费者进行绑定。</li>
                    <li>消息生产者通过<strong>PUBLISH</strong>命令将消息发布到指定的频道。</li>
                    <li>消息消费者通过<strong>SUBSCRIBE</strong>命令订阅感兴趣的频道。</li>
                    <li>当消息生产者发布一条消息到频道后，所有订阅该频道的消费者都会收到消息。</li>
                </ol>
            </div>
            
            <h3 class="heading text-2xl font-bold mt-10 mb-4 text-gray-800">代码实现</h3>
            
            <div class="grid md:grid-cols-3 gap-6 mb-8">
                <div class="bg-white p-5 rounded-lg shadow border border-gray-100">
                    <div class="flex items-center mb-3">
                        <div class="bg-green-100 p-2 rounded-full mr-3">
                            <i class="fas fa-user-check text-green-600"></i>
                        </div>
                        <h4 class="font-bold text-gray-800">1. 消费者订阅channel</h4>
                    </div>
                    <div class="code-block text-sm">
                        <pre><code class="language-java">@Component
public class ConsumerRedisListener implements MessageListener {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    // channel中有消息会调用这个方法
    @Override
    public void onMessage(Message message, byte[] pattern) {
        Object value = stringRedisTemplate.getValueSerializer()
            .deserialize(message.getBody());
        System.out.println("consumer message: " + 
            String.valueOf(value));
        System.out.println("pattern:"+pattern);
    }
}</code></pre>
                    </div>
                </div>
                
                <div class="bg-white p-5 rounded-lg shadow border border-gray-100">
                    <div class="flex items-center mb-3">
                        <div class="bg-yellow-100 p-2 rounded-full mr-3">
                            <i class="fas fa-link text-yellow-600"></i>
                        </div>
                        <h4 class="font-bold text-gray-800">2. 绑定消费者和channel</h4>
                    </div>
                    <div class="code-block text-sm">
                        <pre><code class="language-java">@Configuration
public class RedisConfig {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Autowired
    private ConsumerRedisListener consumerRedisListener;

    // 定义channel
    @Bean
    public ChannelTopic topic() {
        return new ChannelTopic("string-topic");
    }

    @Bean
    public RedisMessageListenerContainer container() {
        RedisMessageListenerContainer container = 
            new RedisMessageListenerContainer();
        container.setConnectionFactory(redisConnectionFactory);
        // 把channel和消费者绑定
        container.addMessageListener(consumerRedisListener, topic());
        return container;
    }
}</code></pre>
                    </div>
                </div>
                
                <div class="bg-white p-5 rounded-lg shadow border border-gray-100">
                    <div class="flex items-center mb-3">
                        <div class="bg-red-100 p-2 rounded-full mr-3">
                            <i class="fas fa-paper-plane text-red-600"></i>
                        </div>
                        <h4 class="font-bold text-gray-800">3. 生产者发送消息</h4>
                    </div>
                    <div class="code-block text-sm">
                        <pre><code class="language-java">// 发布订阅，如果没有订阅情况下发布消息会丢失
@GetMapping("/topic/send")
public void send(String msg) {
    stringRedisTemplate.convertAndSend("string-topic", msg);
}</code></pre>
                    </div>
                </div>
            </div>
        </div>

        <!-- Producer Consumer Section -->
        <div class="prose mx-auto mb-20">
            <h2 class="heading text-3xl font-bold mb-6 text-gray-800 border-b pb-2" id="producer-consumer">生产者消费者模型</h2>
            <p class="text-lg text-gray-700">Redis的列表数据结构支持阻塞操作，其中<strong>BLPOP</strong>和<strong>BRPOP</strong>命令允许客户端在列表为空时阻塞等待新的元素出现。这种阻塞机制使得 Redis 的列表可以用作阻塞队列。</p>
            <p class="text-lg text-gray-700">使用阻塞列表，可以将每个频道看作一个队列。消息发布者将消息发布到频道，而订阅该频道的消费者通过<strong>BLPOP</strong>等命令从列表中取出消息。这样，就实现了简单的队列功能。</p>
            
            <div class="my-8">
                <img src="https://cdn.nlark.com/yuque/0/2024/png/21449790/1705544662458-7607600c-08b8-45bf-94cb-e113a67fb49d.png" alt="生产消费模式图示" class="rounded-lg shadow-lg w-full">
            </div>
            
            <h3 class="heading text-2xl font-bold mt-10 mb-4 text-gray-800">代码实现</h3>
            
            <div class="grid md:grid-cols-2 gap-6 mb-8">
                <div class="bg-white p-5 rounded-lg shadow border border-gray-100">
                    <div class="flex items-center mb-3">
                        <div class="bg-blue-100 p-2 rounded-full mr-3">
                            <i class="fas fa-user-check text-blue-600"></i>
                        </div>
                        <h4 class="font-bold text-gray-800">1. 消费者监听队列</h4>
                    </div>
                    <div class="code-block text-sm">
                        <pre><code class="language-java">@Component
public class ConsumerQueueListener implements Runnable {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    // 启动一个线程轮训list中的数据
    @Override
    public void run() {
        try {
            while (!Thread.currentThread().isInterrupted()) {
                String msg = stringRedisTemplate.opsForList()
                    .rightPop("queue-demo");
                System.out.println("接收到了:" + msg);
            }
        } catch (Exception e) {
            System.err.println(e.getMessage());
        }
    }
}</code></pre>
                    </div>
                </div>
                
                <div class="bg-white p-5 rounded-lg shadow border border-gray-100">
                    <div class="flex items-center mb-3">
                        <div class="bg-purple-100 p-2 rounded-full mr-3">
                            <i class="fas fa-paper-plane text-purple-600"></i>
                        </div>
                        <h4 class="font-bold text-gray-800">2. 生产者发送消息</h4>
                    </div>
                    <div class="code-block text-sm">
                        <pre><code class="language-java">@GetMapping("/topic/send2")
public void send2(String msg) {
    // 发送消息到队列
    stringRedisTemplate.opsForList()
        .leftPush("queue-demo", msg);
}</code></pre>
                    </div>
                </div>
            </div>
            
            <div class="highlight mb-8">
                <h4 class="font-bold text-lg text-gray-800 mb-3">启动消费者线程</h4>
                <div class="code-block text-sm">
                    <pre><code class="language-java">@GetMapping("/start")
public void start(){
    new Thread(consumerQueueListener).start();
}</code></pre>
                </div>
            </div>
        </div>

        <!-- Comparison Section -->
        <div class="prose mx-auto mb-20">
            <h2 class="heading text-3xl font-bold mb-6 text-gray-800 border-b pb-2">两种模式的区别</h2>
            
            <div class="my-8">
                <img src="https://cdn.nlark.com/yuque/0/2024/png/21449790/1705548831028-5a2ff2dd-3fa3-4bc0-9627-c4fa98a937af.png" alt="两种模式区别" class="rounded-lg shadow-lg w-full">
            </div>
            
            <div class="grid md:grid-cols-2 gap-8">
                <div>
                    <h3 class="heading text-xl font-bold mb-3 text-gray-800">生产消费模式</h3>
                    <p class="text-gray-700">可能会存在多个消费者，所以可以提高消息速度。一个消息只能被一个消费者消费，如果出现了多次消费就是属于消息的重复消费。</p>
                    <div class="mt-4 p-4 bg-blue-50 rounded-lg">
                        <h4 class="font-bold text-blue-800 mb-2 flex items-center">
                            <i class="fas fa-lightbulb mr-2"></i>
                            适用场景
                        </h4>
                        <ul class="list-disc pl-5 text-gray-700">
                            <li>扣减库存</li>
                            <li>扣钱</li>
                            <li>修改订单的状态</li>
                            <li>发送短信</li>
                        </ul>
                    </div>
                </div>
                
                <div>
                    <h3 class="heading text-xl font-bold mb-3 text-gray-800">发布订阅模式</h3>
                    <p class="text-gray-700">多个订阅者订阅同一个队列(channel)，一个消息会被多个消费者消费。</p>
                    <div class="mt-4 p-4 bg-purple-50 rounded-lg">
                        <h4 class="font-bold text-purple-800 mb-2 flex items-center">
                            <i class="fas fa-lightbulb mr-2"></i>
                            适用场景
                        </h4>
                        <ul class="list-disc pl-5 text-gray-700">
                            <li>购买了机票会送酒店卷</li>
                            <li>接机卷</li>
                            <li>积分</li>
                            <li>机场服务</li>
                        </ul>
                        <p class="mt-2 text-sm text-purple-700">用户只需要购买机票，相关系统收到通知后各自实现业务逻辑</p>
                    </div>
                </div>
            </div>
        </div>

        <!-- Mermaid Diagram -->
        <div class="prose mx-auto mb-20">
            <h2 class="heading text-3xl font-bold mb-6 text-gray-800 border-b pb-2">模式对比可视化</h2>
            <div class="mermaid">
                graph TD
                    subgraph 生产者消费者模式
                        A[生产者] -->|leftPush| B[(Redis List)]
                        B -->|rightPop| C[消费者1]
                        B -->|rightPop| D[消费者2]
                    end
                    
                    subgraph 发布订阅模式
                        E[发布者] -->|PUBLISH| F[(Redis Channel)]
                        F -->|SUBSCRIBE| G[订阅者1]
                        F -->|SUBSCRIBE| H[订阅者2]
                        F -->|SUBSCRIBE| I[订阅者3]
                    end
            </div>
        </div>
    </div>

    <!-- Footer -->
    <footer class="bg-gray-900 text-gray-300 py-8">
        <div class="container mx-auto px-4 md:px-6">
            <div class="flex flex-col md:flex-row justify-between items-center">
                <div class="mb-4 md:mb-0">
                    <h3 class="text-xl font-bold text-white mb-2">技术小馆</h3>
                    <p class="text-gray-400">专业的IT技术分享平台</p>
                </div>
                <div>
                    <a href="http://www.yuque.com/jtostring" class="text-blue-400 hover:text-blue-300 transition duration-300 flex items-center">
                        <i class="fas fa-external-link-alt mr-2"></i>
                        访问技术小馆
                    </a>
                </div>
            </div>
            <div class="border-t border-gray-800 mt-6 pt-6 text-center text-gray-500 text-sm">
                &copy; 2023 技术小馆. All rights reserved.
            </div>
        </div>
    </footer>

    <script>
        mermaid.initialize({
            startOnLoad: true,
            theme: 'dark',
            flowchart: {
                useMaxWidth: false,
                htmlLabels: true,
                curve: 'basis'
            }
        });
        
        // 平滑滚动
        document.querySelectorAll('a[href^="#"]').forEach(anchor => {
            anchor.addEventListener('click', function (e) {
                e.preventDefault();
                document.querySelector(this.getAttribute('href')).scrollIntoView({
                    behavior: 'smooth'
                });
            });
        });
    </script>
</body>
</html>
```